﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using IMModel.Domain.Common;
using System.Diagnostics;

namespace IMModel.Domain
{
    partial class Respondent : ICacheble, IComparable<Respondent>
    {
        private double? _leaderIndex;
        private double? _harmonyIndex;
        private decimal? _indexToPremium;
        private bool? _isParticipant;


        /// <summary>
        /// Получает итоговый индекс респондента
        /// </summary>
        public double Index { get { return GetIndex(); } }

        /// <summary>
        /// Получает индекс лидерства респондента
        /// </summary>
        public double LeaderIndex { get { return GetLeaderIndex(); } }

        /// <summary>
        /// Получает индекс гармонии респондента
        /// </summary>
        public double HarmonyIndex { get { return GetHarmonyIndex(); } }

        /// <summary>
        /// Получает величину стимулирующей надбавки для респондента
        /// </summary>
        public decimal Premium { get { return GetIndexToPremium(); } }

        /// <summary>
        /// Получает коллекцию секций, по которым респондент лидирует
        /// </summary>
        public IEnumerable<SectionTerm> LeaderIndexSections
        {
            get
            {
                Contract.Requires(Term != null);
                Contract.Requires(IsParticipant);
                Contract.Ensures(Contract.Result<IEnumerable<SectionTerm>>() != null);
                return Term.GetLeaderIndexSections(this);
            }
        }

        /// <summary>
        /// Возвращает признак, является ли данный респондент участником индексной системы.
        /// На данный момент участниками индексной системы являются респонденты, имеющие отличные от ноля объёмы 
        /// учебной нагрузки. Так же для респондента не должен быть установлен атрибут IgnoreVolumes
        /// </summary>
        public bool IsParticipant
        {
            get
            {
                Contract.Ensures(_isParticipant.HasValue);

                if (_isParticipant == null)
                    _isParticipant = HasRequiredCharacteristicAnswers && !IgnoreVolumes;

                return _isParticipant.Value;
            }
        }

        private readonly IDictionary<Term, bool> _isParticipantCache = new Dictionary<Term, bool>();
        /// <summary>
        /// Получает признак того, что респондент является участником индексной системы в заданном терме
        /// </summary>
        /// <param name="term">Терм</param>
        /// <returns>Истина, если в заданном периоде респондент является участником</returns>
        public bool IsParticipantInTerm(Term term)
        {
            Contract.Requires(term != null);

            if (_isParticipantCache.ContainsKey(term))
                return _isParticipantCache[term];
            _isParticipantCache[term] = term.Respondents.Where(x => x.Person == this.Person).Any(x => x.IsParticipant);
            return _isParticipantCache[term];
        }

        private readonly IDictionary<Term, Respondent> _meParticipantCache = new Dictionary<Term, Respondent>();
        /// <summary>
        /// Получает респондента заданного периода, персона которого соответствует этому респонденту
        /// </summary>
        /// <param name="term">Период</param>
        /// <returns></returns>
        public Respondent GetMeInOtherTerm(Term term)
        {
            Contract.Requires(term != null);
            Contract.Requires(IsParticipantInTerm(term));
            Contract.Ensures(Contract.Result<Respondent>() != null);
            Contract.Ensures(Contract.Result<Respondent>() == _meParticipantCache[term]);


            if (_meParticipantCache.ContainsKey(term)) return _meParticipantCache[term];

            //var rc = term.Respondents.Count(x => (x.Person == this.Person) && x.FlagOsnJob);
            //Debug.Assert(rc == 1, "term.Respondents.Count = " + rc + " для Person = " + this.Person);
            //if (rc != 1) _meParticipantCache[term] = new Respondent() { Person = new Person() { FirstName = "Фиктивный персонаж" } };
            //else

            try
            {
                _meParticipantCache[term] = term.Respondents.Single(x => (x.Person == this.Person) && x.FlagOsnJob);
            }
            catch
            {
                var rc = term.Respondents.Count(x => (x.Person == this.Person) && x.FlagOsnJob);
                var s = "Рассчёт невозможен, потомучто для " + this.Person;
                if (rc == 0) throw new Exception(s + " не указано основное место работы");
                throw new Exception(s + " указано несколько основных мест работы");
            }
            return _meParticipantCache[term];
        }


        /// <summary>
        /// Получает признак, что респондент имеет необходимые ответы по показателям для того, что бы участвовать в индексной системе
        /// </summary>
        private bool HasRequiredCharacteristicAnswers
        {
            get
            {
                return Person.Respondents.Where(x => x.Term == Term).Any(r =>
                    r.Answers.Where(x => x.CharacteristicTerm.IndexRequired).Select(x =>
                        x.CharacteristicTerm).Distinct().Count() == Term.RequiredIndexCount);
                //return true;
                //return Person.Respondents.Where(x=>x.Term == Term).Any(r=>
                //  r.Answers.Where(x => x.GradationCharacteristic.CharacteristicTerm.IndexRequired).Select(x => 
                //    x.GradationCharacteristic.CharacteristicTerm).Distinct().Count() == Term.RequiredIndexCount);
            }
        }

        /// <summary>
        /// Получает усреднённый объём оценки деятельности респондента непосредственным руководителем
        /// </summary>
        public double AverageRespondentMarkVolume
        {
            get
            {
                Contract.Requires(HasMark);
                return Answers.AsEnumerable().Where(x => x.IsMark).Average(x => x.Value);
            }
        }

        private decimal GetIndexToPremium()
        {
            Contract.Requires(IsParticipant);
            Contract.Ensures(_indexToPremium.HasValue);
            if (_indexToPremium == null) _indexToPremium = Term.GetRespondentPremium(this);
            return _indexToPremium.Value;
        }


        private double GetHarmonyIndex()
        {
            Contract.Requires(IsParticipant);
            Contract.Requires(Term != null);
            Contract.Ensures(_harmonyIndex.HasValue);

            if (_harmonyIndex == null)
                _harmonyIndex = Term.GetHarmonyIndex(this);

            return _harmonyIndex.Value;
        }

        private double GetLeaderIndex()
        {
            Contract.Requires(IsParticipant);
            Contract.Ensures(_leaderIndex.HasValue);
            if (_leaderIndex == null)
                _leaderIndex = Term.GetLeaderIndex(this);

            return _leaderIndex.Value;
        }

        private double GetIndex()
        {
            Contract.Requires(IsParticipant);
            return Term[this];
        }

        public void Invalidate()
        {
            Contract.Ensures(_leaderIndex == null);
            Contract.Ensures(_harmonyIndex == null);
            Contract.Ensures(_indexToPremium == null);
            Contract.Ensures(_isParticipant == null);
            Contract.Ensures(_isParticipantCache.Count == 0);

            _leaderIndex = null;
            _harmonyIndex = null;
            _indexToPremium = null;
            _isParticipant = null;
            _isParticipantCache.Clear();

        }

        public int CompareTo(Respondent other)
        {
            return Person.FullName.CompareTo(other.Person.FullName);
        }

        /// <summary>
        /// Получает период к которому принадлежит респондент
        /// </summary>
        public Term Term
        {
            get
            {
                Contract.Requires(DepartmentTerm != null);
                Contract.Ensures(Contract.Result<Term>() != null);
                return DepartmentTerm.Term;
            }
        }

        /// <summary>
        /// Получает отдел в котором работает респондент
        /// </summary>
        public Department Department
        {
            get
            {
                Contract.Requires(DepartmentTerm != null);
                return DepartmentTerm.Department;
            }
        }

        /// <summary>
        /// Получает признак, что респондент имеет оценки работы руководителем
        /// </summary>
        public bool HasMark
        {
            get
            {
                return Answers.AsEnumerable().Any(x => x.IsMark);
            }
        }

        /// <summary>
        /// Получает признак того, что респондент участвует в распределении фонда материального стимулирования
        /// </summary>
        public bool IsPremiumParticipant
        {
            get
            {
                return IsParticipant && !Dismissed;
            }
        }

        public double GetCharacteristicVolume(CharacteristicTerm characteristicTerm)
        {
            Contract.Requires(IsParticipant);
            Contract.Requires(characteristicTerm != null);
            Contract.Requires(characteristicTerm.Term == DepartmentTerm.Term);
            return 0.0F;
        }

        public override string ToString()
        {
            Contract.Requires(Person != null);
            return Person.ToString();
        }
    }
}